Add data_base64 support and simplify CE JSON marshaling#40
Conversation
- RawEvent is now a standalone struct with custom MarshalJSON/UnmarshalJSON that handles both "data" and "data_base64" per CloudEvents JSON spec, including round-trip preservation and MIME-based wire form selection. - CloudEvent[A] stores data_base64 opaquely without decoding; consumers decide how to handle it. - Simplify unmarshalCloudEventWithPayload by using the cloudEventHeader type alias for known fields instead of per-field deserialization. - Replace headerToMap helper with sjson for consistent header serialization. - Add BytesForSignature and IsJSONDataContentType helpers. - Reject events with both "data" and "data_base64" present. - Add comprehensive positive and negative tests for all new behavior. BREAKING: RawEvent changed from type alias to standalone struct. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
This PR adds support for the CloudEvents data_base64 field per the CloudEvents JSON Event Format specification, enabling proper handling of binary or non-JSON payloads. The implementation redesigns RawEvent from a type alias to a standalone struct with custom JSON marshaling/unmarshaling, and adds DataBase64 field support to CloudEvent[A] with different semantics suited to each type's use case.
Changes:
- Add
data_base64support with automatic encoding/decoding based on content type and round-trip preservation - Convert
RawEventfrom type alias to struct with fulldata_base64handling (decodes into Data field) - Add
DataBase64field toCloudEvent[A](stored opaquely without decoding) - Simplify JSON marshaling by using
sjsonlibrary and type aliases instead of custom helper functions - Add
BytesForSignature()andIsJSONDataContentType()helper methods - Comprehensive test coverage for positive and negative cases including round-trip preservation
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| cloudevent.go | Converted RawEvent to standalone struct, added DataBase64 field to CloudEvent[A], implemented custom marshal/unmarshal for RawEvent with data_base64 support, added BytesForSignature and IsJSONDataContentType helpers |
| cloudevent_json.go | Refactored CloudEvent[A] marshal/unmarshal to support data_base64, simplified implementation using sjson and type aliases, added unmarshalCloudEventWithPayload for shared logic |
| cloudevent_test.go | Added comprehensive tests for data_base64 support in both CloudEvent[A] and RawEvent, including round-trip, error cases, wire format selection, and helper functions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Data contains domain-specific information about the event. | ||
| Data A `json:"data"` | ||
|
|
||
| DataBase64 string `json:"data_base64,omitempty"` |
There was a problem hiding this comment.
The json tag on the DataBase64 field includes omitempty, but this tag has no effect because CloudEvent[A] has a custom MarshalJSON method that explicitly controls which field to emit (data vs data_base64). Consider removing the json tag entirely or changing it to json:"-" to make it clear that this field is not directly marshaled by the standard JSON encoder, preventing potential confusion for future maintainers.
| DataBase64 string `json:"data_base64,omitempty"` | |
| DataBase64 string `json:"-"` |
| // data_base64 (CloudEvents spec). When set, MarshalJSON emits data_base64 for | ||
| // round-trip; otherwise wire form is chosen from DataContentType and Data. |
There was a problem hiding this comment.
The comment on line 117 mentions "data_base64 (CloudEvents spec)" but should clarify that this refers to the CloudEvents JSON format specification. Consider being more specific, e.g., "data_base64 field per CloudEvents JSON Event Format" with a reference to the spec version if appropriate.
| // data_base64 (CloudEvents spec). When set, MarshalJSON emits data_base64 for | |
| // round-trip; otherwise wire form is chosen from DataContentType and Data. | |
| // the data_base64 field per the CloudEvents JSON Event Format (for example, | |
| // CloudEvents v1.0.2). When set, MarshalJSON emits data_base64 for round-trip; | |
| // otherwise wire form is chosen from DataContentType and Data. |
| Data json.RawMessage `json:"data,omitempty"` | ||
|
|
||
| // DataBase64 is the raw "data_base64" string when the event was received with | ||
| // data_base64 (CloudEvents spec). When set, MarshalJSON emits data_base64 for | ||
| // round-trip; otherwise wire form is chosen from DataContentType and Data. | ||
| DataBase64 string `json:"data_base64,omitempty"` |
There was a problem hiding this comment.
The json tags on the Data and DataBase64 fields include omitempty, but these tags have no effect because RawEvent has a custom MarshalJSON method that explicitly controls which field to emit. Consider removing the json tags entirely or changing them to json:"-" to make it clear that these fields are not directly marshaled by the standard JSON encoder.
| Data json.RawMessage `json:"data,omitempty"` | |
| // DataBase64 is the raw "data_base64" string when the event was received with | |
| // data_base64 (CloudEvents spec). When set, MarshalJSON emits data_base64 for | |
| // round-trip; otherwise wire form is chosen from DataContentType and Data. | |
| DataBase64 string `json:"data_base64,omitempty"` | |
| Data json.RawMessage `json:"-"` | |
| // DataBase64 is the raw "data_base64" string when the event was received with | |
| // data_base64 (CloudEvents spec). When set, MarshalJSON emits data_base64 for | |
| // round-trip; otherwise wire form is chosen from DataContentType and Data. | |
| DataBase64 string `json:"-"` |
Summary
data_base64support toRawEvent:RawEventis now a standalone struct (no longer a type alias) with customMarshalJSON/UnmarshalJSONthat handles both"data"and"data_base64"per the CloudEvents JSON spec, including round-trip preservation and MIME-based wire form selection.headerToMaphelper withsjsonfor consistent header serialization acrossCloudEventHeader,CloudEvent[A], andRawEvent. SimplifyunmarshalCloudEventWithPayloadby using thecloudEventHeadertype alias instead of per-field deserialization.CloudEvent[A]storesdata_base64opaquely without decoding; consumers decide how to handle it.BytesForSignature()for signature verification andIsJSONDataContentType()for MIME detection."data"and"data_base64"are present.Breaking changes
RawEventchanged fromtype RawEvent = CloudEvent[json.RawMessage]to a standalone struct. Callers constructingRawEventviaCloudEvent[json.RawMessage]{...}will need to update.Test plan
Made with Cursor